# -*- coding: utf-8 -*-

import os
import sys
import re
import locale
from yamllint import linter
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint.cli import Format

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

PROBLEM_LEVELS = {
    0: None,
    1: 'warning',
    2: 'error',
    None: 0,
    'warning': 1,
    'error': 2,
}


def dir_exclude_hit(dir_path, exclude_dir):
    list_dir = os.listdir(dir_path)
    if isinstance(exclude_dir, (list, tuple)):
        return [_hit for _hit in list_dir if _hit not in exclude_dir or os.path.isfile(os.path.join(dir_path, _hit))]
    else:
        return [_hit for _hit in list_dir if
                re.compile(exclude_dir).match(_hit) is None or os.path.isfile(os.path.join(dir_path, _hit))]


def dir_include_hit(dir_path, include_dir):
    list_dir = os.listdir(dir_path)
    if isinstance(include_dir, (list, tuple)):
        return [_hit for _hit in list_dir if _hit in include_dir or os.path.isfile(os.path.join(dir_path, _hit))]
    else:
        return [_hit for _hit in list_dir if
                re.compile(include_dir).match(_hit) or os.path.isfile(os.path.join(dir_path, _hit))]


def file_type_judgment(regex, path):
    """
    文件格式正则匹配
    @param regex: 正则表达式
    @param path: 单个文件路径
    :return:
    """
    try:
        re_c = re.compile(r'{}'.format(regex))
        return re_c.search(path)
    except Exception:
        print('Error: Regular pattern error! Unreasonable regex as following: ')
        print('%s' % regex)
        print('Please check and try again!')
        sys.exit(1)


def handler_yaml_files(yaml_files, split_chart=',; '):
    """
    处理外部引入的yaml参数，把分隔符分隔的字符串转成列表
    @param yaml_files: 可以传以分隔符分隔的多个yaml文件路径
    @param split_chart: 定义分隔符, default=',; '
    :return: yaml file path list
    """
    yaml_file_list = []
    try:
        if isinstance(yaml_files, (list, tuple)):
            yaml_file_list = list(yaml_files)
        elif isinstance(yaml_files, str):
            res_list = [yaml_files]
            for sc in split_chart:
                tmp_list = []
                map(lambda x: tmp_list.extend(x.split(sc)), res_list)
                res_list = tmp_list
            # 当字符串元素中存在连续的分隔符时，就会出现空格的，得去除
            yaml_file_list = [line for line in res_list if line]
        return yaml_file_list
    except Exception as e:
        raise TypeError("Unknown error when handler yaml path list: {}".format(e))


def walk_files(dir_path=None, yaml_files=None, include_dir=None, exclude_dir=None, exclude_file=None):
    """
    递归查找指定目录下的指定后缀的文件，可指定被递归的子目录名，可指定要从递归中排除的子目录名，可指定要排除的特定正则的文件
    @param dir_path:        目录，默认值是 /pitrix/conf/variables
    @param yaml_files:      被识别的文件后缀，支持正则匹配，.e.g：r'(.*).yaml'
    @param include_dir:     指定当前dir_path路径下，要递归了子目录名，支持列表、正则
    @param exclude_dir:     需要从递归扫描中排除的目录名, 支持列表、正则
    @param exclude_file:    需要从扫描中排除的文件, 正则匹配
    :return:                yield
    """
    # 不可同时使用include和exclude
    if all([include_dir, exclude_dir]):
        print("Error: parameter conflict when recursive use <include_dir> and <exclude_dir> at the same time!!")
        sys.exit(1)
    # 首次递归，如果include_dir不为空，以include_dir的目录初始生成目录列表
    if include_dir:
        list_dirs = dir_include_hit(dir_path, include_dir)
    # 首次递归，如果exclude_dir不为空，以exclude_dir的目录初始生成目录列表
    elif exclude_dir:
        list_dirs = dir_exclude_hit(dir_path, exclude_dir)
    else:
        list_dirs = os.listdir(dir_path)

    for list_dir in list_dirs:
        path = os.path.join(dir_path, list_dir)
        if os.path.isfile(path):
            if file_type_judgment(yaml_files, path) is not None:
                if exclude_file is not None and file_type_judgment(exclude_file, path) is None:
                    yield path
                elif exclude_file is None:
                    yield path
        # 如果遍历的路径还是目录，判断路径是否存在，如果存在则继续递归
        elif os.path.isdir(path):
            for _file in walk_files(dir_path=path, yaml_files=yaml_files, include_dir=None, exclude_dir=None,
                                    exclude_file=exclude_file):
                yield _file


def show_problems(problems, yaml_file, no_warn, error_header_print):
    max_level = 0
    first = True
    result = None
    for problem in problems:
        max_level = max(max_level, PROBLEM_LEVELS[problem.level])
        if no_warn and (problem.level != 'error'):
            continue
        else:
            if error_header_print:
                print("Error: While check yaml syntax, some problems as following: ")
                error_header_print = False
            if result is None:
                result = {}
            if first:
                print(yaml_file)
                result.update({yaml_file: []})
                first = False
            fprint = Format.standard(problem, yaml_file)
            result[yaml_file].append(fprint)
            print(fprint)
    if not first:
        print('')
    return max_level, result, error_header_print


def check_one_yaml(conf, yaml_file, problems_res, max_level, error_header_print, no_warn):
    """
    @param conf:                yamllint config, type buffer
    @param yaml_file:           yaml file
    @param problems_res:        return problems result, type dict()
    @param max_level:           yamllint max error level
    @param error_header_print:  ensure is printed error header text first, if it finds errors
    @param no_warn:             yamllint warning print or not.
    @return:                    max_level, problems_res, error_header_print
    """
    try:
        with open(yaml_file, 'r') as f:
            problems = linter.run(f, conf, yaml_file)
    except EnvironmentError as e:
        print("Error: While walk yaml files, has exception [{}]".format(e))
        sys.exit(-1)

    prob_level, prob_res, error_header_print = show_problems(problems, yaml_file, no_warn, error_header_print)
    max_level = max(max_level, prob_level)

    if problems_res is None:
        problems_res = {}
    if prob_res:
        problems_res.update(prob_res)

    return max_level, problems_res, error_header_print


def check_yaml_files(dir_path=None, yaml_files=None, yaml_lint_conf=None, strict=False, is_exit=True, no_warn=True, include_dir=None, exclude_dir=None, exclude_file=None):
    """
    @param dir_path:        目录，默认值是 /pitrix/conf/variables
    @param yaml_files:      被识别的文件后缀，支持正则匹配，.e.g：r'(.*).yaml'
    @param include_dir:     指定当前dir_path路径下，要递归了子目录名，支持列表、正则 .e.g: ['boss2', 'global']
    @param exclude_dir:     需要从递归扫描中排除的目录名, 支持列表、正则
    @param exclude_file:    需要从扫描中排除的文件, 支持正则匹配
    @param yaml_lint_conf   指定yamllint的配置文件
    @param strict           是否使用<严格>模式，即Warning也算错误
    @param is_exit          语法出现问题时是否直接退出 sys.exit(code)
    @param no_warn          warning级别信息是否打印
    @return:
    """
    if not dir_path and not yaml_files:
        print("Error: parameter <dir_path> or <yaml_files> can not be none.")
        sys.exit(1)
    try:
        if yaml_lint_conf is not None:
            conf = YamlLintConfig(file=yaml_lint_conf)
        elif os.path.isfile(os.path.join(BASE_DIR, '.yamllint')):
            conf = YamlLintConfig(file=os.path.join(BASE_DIR, '.yamllint'))
        else:
            conf = YamlLintConfig('extends: default')

    except YamlLintConfigError as e:
        print("Error: While load yaml lint conf, has exception [{}]".format(e))
        sys.exit(-1)

    if conf.locale is not None:
        locale.setlocale(locale.LC_ALL, conf.locale)

    max_level = 0
    problems_res = None
    error_header_print = True
    if dir_path and yaml_files:
        for yaml_file in walk_files(dir_path=dir_path, yaml_files=yaml_files, include_dir=include_dir, exclude_dir=exclude_dir, exclude_file=exclude_file):
            max_level, problems_res, error_header_print = check_one_yaml(conf, yaml_file, problems_res, max_level, error_header_print, no_warn)
    elif yaml_files and not dir_path:
        for yaml_file in handler_yaml_files(yaml_files):
            max_level, problems_res, error_header_print = check_one_yaml(conf, yaml_file, problems_res, max_level, error_header_print, no_warn)
    else:
        for yaml_file in walk_files(dir_path=dir_path, yaml_files=r'*.yaml|*.yml', include_dir=include_dir, exclude_dir=exclude_dir, exclude_file=exclude_file):
            max_level, problems_res, error_header_print = check_one_yaml(conf, yaml_file, problems_res, max_level, error_header_print, no_warn)

    if max_level == PROBLEM_LEVELS['error']:
        return_code = 1
    elif max_level == PROBLEM_LEVELS['warning']:
        return_code = 2 if strict else 0
    else:
        return_code = 0

    if is_exit:
        sys.exit(return_code)
    else:
        return return_code, problems_res
